/**
 * 
 * @Project le cOeur - Data Virtualization
 * @Date 08.12.2012
 * @Author Aebi Pascal, Kocher Marc
 * @School BFH-TI Biel, Bachelor Thesis
 *
 */

package main.framework;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;

import main.framework.cache.CacheManager;
import main.framework.cache.CacheStrategy;
import main.framework.connection.Protocol;
import main.framework.connection.ServerSocketFactory;
import main.framework.exception.PortListenerException;
import main.framework.exception.ResourceManagerException;
import main.framework.exception.RootElementNotFoundException;
import main.framework.exception.ServerStatusException;
import main.framework.representation.RepresentationCreator;
import main.framework.representation.RepresentationCreatorManager;
import main.framework.resource.ResourceManager;
import main.framework.security.AccessList;
import main.framework.security.KeyStoreManager;
import main.framework.security.ExtendedSecurityManager;
import main.framework.setting.Config;

/**
 * This class represents the HTTP server.
 */
public class HTTPServer implements Service {
	
	private KeyStoreManager keyStoreManager;
	private PortListenerManager portListenerManager;
	private ExtendedSecurityManager securityManager;
	private CacheManager cacheManager;
	private ResourceManager resourceManager;
	private RepresentationCreatorManager representationCreatorManager;
	private ExecutorService threadPool;
	private RequestHandler requestHandler;
	private Status status;
	private Logger logger;
	private boolean isInitialized;
	private ServerSocketFactory serverSocketFactory;
	
	/**
	 * Creates a HTTP server.
	 * 
	 * @throws KeyStoreException If no Provider supports a KeyManagerFactorySpi implementation for the specified algorithm.
	 * @throws NoSuchAlgorithmException If no Provider supports a KeyManagerFactorySpi implementation for the specified algorithm.
	 * @throws URISyntaxException 
	 */
	public HTTPServer() throws KeyStoreException, NoSuchAlgorithmException, URISyntaxException {
		this.keyStoreManager = new KeyStoreManager();
		this.serverSocketFactory = new ServerSocketFactory(this.keyStoreManager);
		this.portListenerManager = new PortListenerManager(this.serverSocketFactory);
		this.cacheManager = new CacheManager();
		this.resourceManager = new ResourceManager(Config.ROOT_RESOURCE_PATH);
		this.representationCreatorManager = new RepresentationCreatorManager();
//		this.threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*25);
		this.threadPool = Executors.newCachedThreadPool();
		this.securityManager = new ExtendedSecurityManager();
		this.requestHandler = new RequestHandler(this.resourceManager, this.representationCreatorManager, this.cacheManager, this.securityManager);
		this.logger = Logger.getLogger("SysLogger");
		this.status = Status.STOPPED;
		this.isInitialized = false;
	}
	
	@Override
	public void init() throws UnrecoverableKeyException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, KeyStoreException, IOException, NumberFormatException, PortListenerException {
		this.keyStoreManager.initKeyStores();
		if(Config.ENABLE_DEFAULT_PORTS.equals("1")){
			this.addDefaultPortListeners();
		}
		this.isInitialized = true;
		this.logger.info("HTTP server initialized.");
	}
	
	@Override
	public void start() throws ServerStatusException, ResourceManagerException, KeyManagementException, NoSuchAlgorithmException, PortListenerException, IOException  {
		if(this.resourceManager.getRootResource() == null){
			throw new ResourceManagerException("There are no resources defined.");
		}
		else if(!this.isInitialized){
			throw new ServerStatusException("The HTTP server can not be started until it is initialized once.");
		}
		else if(this.status == Status.STARTED){
			throw new ServerStatusException("The HTTP server is already started.");
		}
		else {
			this.portListenerManager.enablePortListeners();
			this.status = Status.STARTED;
			this.logger.info("HTTP server started.");
		}
	}
	
	@Override
	public void stop() throws ServerStatusException, IOException, PortListenerException {
		if(this.status == Status.STOPPED){
			throw new ServerStatusException("The HTTP server is already stopped.");
		}
		else{
			this.portListenerManager.disablePortListeners();
			this.status = Status.STOPPED;
			this.logger.info("HTTP server stopped.");
		}
	}
	
	@Override
	public boolean isStarted() {
		if(this.status == Status.STARTED){
			return true;
		}
		else{
			return false;
		}
	}
	
	/**
	 * Adds a representation creator to the HTTP server.
	 * It can be defined if the representation creator should be the default creator.
	 * If an other representation creator is set as default, the state will be overwritten if set to true.
	 * 
	 * @param representationCreator The representation creator.
	 * @param isDefault True if the creator should be defined as default, else false.
	 */
	public void addRepresentationCreator(RepresentationCreator representationCreator, boolean isDefault) {
		this.representationCreatorManager.attachRepresentationCreator(representationCreator, isDefault);
	}
	
	/**
	 * Removes the representation creator corresponding to the given content type.
	 * 
	 * @param contentType The content type of the creator.
	 */
	public void removeRepresentationCreator(String contentType) {
		this.representationCreatorManager.detachRepresentationCreator(contentType);
	}
	
	/**
	 * Adds the default HTTP and HTTPS ports to the HTTP server.
	 * Default HTTP port is port 80.
	 * Default HTTPS port is port 443.
	 * After adding the ports, the listeners need to be started.
	 * 
	 * @throws NumberFormatException If the string does not contain a parsable integer.
	 * @throws PortListenerException If a port listener could not be attached because it is already attached.
	 */
	public void addDefaultPortListeners() throws NumberFormatException, PortListenerException {
		this.portListenerManager.attachPortListener(new PortListener(Protocol.HTTP, Integer.parseInt(Config.DEFAULT_HTTP_PORT), this.threadPool, this.requestHandler));
		this.portListenerManager.attachPortListener(new PortListener(Protocol.TLS, Integer.parseInt(Config.DEFAULT_TLS_PORT), this.threadPool, this.requestHandler));
	}

	/**
	 * Removes the default HTTP and HTTPS port listener from the HTTP server.
	 * Default HTTP port is port 80.
	 * Default HTTPS port is port 443.
	 * 
	 * @throws NumberFormatException If the string does not contain a parsable integer.
	 * @throws IOException If an I/O error occurs when closing the socket.
	 * @throws PortListenerException If a port listener could not be disabled because it is already disabled.
	 */
	public void removeDefaultPortListeners() throws NumberFormatException, IOException, PortListenerException {
		this.portListenerManager.detachPortListener(Integer.parseInt(Config.DEFAULT_HTTP_PORT));
		this.portListenerManager.detachPortListener(Integer.parseInt(Config.DEFAULT_TLS_PORT));
	}
	
	/**
	 * Adds a specific port listener to the HTTP server.
	 * After adding the port, the listener needs to be started.
	 * 
	 * @param protocol The protocol (HTTP or HTTPS).
	 * @param port The port number.
	 * @throws PortListenerException If the port listener could not be attached because it is already attached.
	 */
	public void addPortListener(Protocol protocol, int port) throws PortListenerException {
		this.portListenerManager.attachPortListener(new PortListener(protocol, port, this.threadPool, this.requestHandler));
	}

	/**
	 * Removes a specific port listener from the HTTP server.
	 * 
	 * @param port The listening port number.
	 * @throws IOException If an I/O error occurs when closing the socket.
	 * @throws PortListenerException If the port listener could not be disabled because it is already disabled.
	 */
	public void removePortListener(int port) throws IOException, PortListenerException {
		this.portListenerManager.detachPortListener(port);
	}
	
	/**
	 * Enables a specific port listener if it is available and disabled.
	 * 
	 * @param port The port number.
	 * @throws KeyManagementException If the init operation fails.
	 * @throws NoSuchAlgorithmException If no Provider supports a TrustManagerFactorySpi implementation for the specified protocol.
	 * @throws PortListenerException If the port listener could not be enabled because it is already enabled.
	 * @throws IOException For networking errors.
	 */
	public void enablePortListener(int port) throws KeyManagementException, NoSuchAlgorithmException, PortListenerException, IOException {
		this.portListenerManager.enablePortListener(port);
	}
	
	/**
	 * Disables a specific port listener if it is available and enabled.
	 * 
	 * @param port The port number.
	 * @throws IOException If an I/O error occurs when closing the socket.
	 * @throws PortListenerException If the port listener could not be disabled because it is already disabled.
	 */
	public void disablePortListener(int port) throws IOException, PortListenerException {
		this.portListenerManager.disablePortListener(port);
	}
	
	/**
	 * Sets the caching strategy of the HTTP server.
	 * 
	 * @param cacheStrategy The cache strategy.
	 */
	public void setCacheStrategy(CacheStrategy cacheStrategy) {
		this.cacheManager.setCacheStrategy(cacheStrategy);
	}
	
	/**
	 * Sets a server access list. The list could either be a white or a black list.
	 * Only one of the two possibilities can be set.
	 * 
	 * @param accessList The access list.
	 */
	public void setServerAccess(AccessList accessList) {
		this.securityManager.setServerAccess(accessList);
	}
	
	/**
	 * Creates resources from the given XML file.
	 * 
	 * @param filePath The file with its path.
	 * @throws InstantiationException If this Class represents an abstract class, an interface, an array class, a primitive type, or void; or if the class has no nullary constructor; or if the instantiation fails for some other reason.
	 * @throws IllegalAccessException If the class or its nullary constructor is not accessible.
	 * @throws ClassNotFoundException If the class cannot be located.
	 * @throws ParserConfigurationException If a DocumentBuilder cannot be created which satisfies the configuration requested.
	 * @throws SAXException If any parse errors occur.
	 * @throws IOException If any IO errors occur.
	 * @throws RootElementNotFoundException If the root element is not found.
	 * @throws URISyntaxException If the given string violates RFC 2396.
	 */
	public void createResources(String filePath) throws InstantiationException, IllegalAccessException, ClassNotFoundException, ParserConfigurationException, SAXException, IOException, RootElementNotFoundException, URISyntaxException {
		this.resourceManager.parseXMLFile(filePath);
	}

}
